<?php
/**
 * This file contains the monitor controller to manage and monitor the resque.
 *
 * @author Vincent.Hou <vincenthou@augmentum.com.cn>
 */

/**
 * MonitorController class impletement the management and monitoring of resque.
 *
 * @package system\resque\controllers
 * @author Vincent.Hou <vincenthou@augmentum.com.cn>
 *
 */
class MonitorController extends ResqueBaseController
{
    public $defaultAction = 'default';

    /**
     * Render the default login page
     */
    public function actionDefault()
    {
        $this->loadResource();
        if (Constant::$IS_LOGINED == Yii::app()->session['isLogined'])
        {
            $this->renderDashboard();
        }
        else
        {
            $this->render('login');
        }
    }

    /**
     * Render the main page
     */
    public function actionLogin()
    {
        $password = Yii::app()->request->getPost('password');
        $this->loadResource();
        if (isset($password) && $password == Yii::app()->modules['gii']['password'])
        {
            Yii::app()->session['isLogined'] = Constant::$IS_LOGINED;
            $this->renderDashboard();
        }
        else
        {
            $this->render('login');
        }
    }

    /**
     * Render the management page
     */
    public function actionIndex()
    {
        $this->loadResource();
        $this->renderDashboard();
    }

    /**
     * Confirm the workers creation configuration in the main.php file
     */
    public function actionConfirm()
    {
        $request = Yii::app()->request;
        $workerConfig = Yii::app()->resque->workers;
        if ($this->resquestTokenValid($request->getPost('request-token')))
        {
            $configText = '';
            $resquePath = YiiBase::getPathOfAlias('application.modules.resque.components');
            foreach ($workerConfig as $queue => $amount)
            {
                for ($i = 0; $i < $amount; $i++)
                {
                    $configText .= $this->generateConfigText(array('queue' => $queue), $resquePath);
                }
            }
            $this->configWorker($configText, $resquePath);

            $this->redirect(Yii::app()->createUrl('/resque/job'));
        }
        else
        {
            echo "Invalid resquest.";
        }
    }

    /**
     * Creating worker page
     */
    public function actionCreate()
    {
        $request = Yii::app()->request;
        if ($this->resquestTokenValid($request->getPost('request-token')))
        {
            $config = array();
            $config['queue'] = $request->getPost('queue');
            $config['count'] = $request->getPost('count');
            $config['ip'] = $request->getPost('ip');
            $config['port'] = $request->getPost('port');
            $config['password'] = $request->getPost('password');
            $config['interval'] = $request->getPost('interval');
            $config['appInclude'] = $request->getPost('app-include');
            $config['prefix'] = $request->getPost('redis-prefix');

            $resquePath = YiiBase::getPathOfAlias('application.modules.resque.components');
            $configText = $this->generateConfigText($config, $resquePath);
            $this->configWorker($configText, $resquePath);

            $this->redirect(Yii::app()->createUrl('/resque/job'));
        }
        else
        {
            echo "Invalid resquest.";
        }
    }

    private function renderDashboard()
    {
        $resque = Yii::app()->resque;
        $curRedis = $resque->redis();
        $data = array();
        $info = $curRedis->info();
        $prefixCount = strlen($curRedis->getPrefix());
        $keys = $curRedis->keys('*');
        foreach ($keys as $key)
        {
            $data[$key] = array();
            $rawKey = substr($key, $prefixCount);
            $type = $curRedis->type($rawKey);
            $data[$key]['type'] = $type;
            $data[$key]['value'] = $this->getValueByType($curRedis, $rawKey, $type);
        }
        //create request token
        $requestToken = $this->generateRequestToken($curRedis);

        $workerConfig = $resque->workers;
        $this->render('index', array('data' => $data, 'info' => $info, 'workerConfig' => $workerConfig, 'requestToken' => $requestToken));
    }

    /**
     * Generate the configuration text to be appended to the supervisord config file
     * @param  array $config the configuration
     * @param  string $resquePath resquest component path
     * @throws Exception if the resque path is not specified
     */
    private function generateConfigText($config, $resquePath)
    {
        if (!isset($resquePath))
        {
            echo new Exception("Resque path should be specified.");
        }

        $configText = PHP_EOL . PHP_EOL;
        $configText .= '[program:' . $config['queue'] . uniqid() . 'Worker]' . PHP_EOL;
        $configText .= 'environment=QUEUE="';
        $configText .= (empty($config['queue']) ? '*' : $config['queue'] . '"');
        !empty($config['prefix']) && ($configText .= ', PREFIX="' . $config['prefix'] . '"');
        !empty($config['appInclude']) && ($configText .= ', APP_INCLUDE="' . $config['appInclude'] . '"');
        !empty($config['count']) && ($configText .= ', COUNT="' . $config['count'] . '"');
        !empty($config['ip']) && !empty($config['port']) && ($configText .= ', REDIS_BACKEND="' . $config['ip'] . ':' . $config['port'] . '"');
        !empty($config['password']) && ($configText .= ', REDIS_AUTH="' . $config['password'] . '"');
        !empty($config['interval']) && ($configText .= ', INTERVAL="' . $config['interval'] . '"');

        $configText.= PHP_EOL .
            'command=php '. $resquePath .'/bin/resque' . PHP_EOL .
            'numprocs=1' . PHP_EOL .
            'stderr_logfile_maxbytes=10MB' . PHP_EOL .
            'redirect_stderr=true' . PHP_EOL .
            'autostart=true' . PHP_EOL .
            'autorestart=true' . PHP_EOL;

        return $configText;
    }

    /**
     * Open the supervisord file and write the configuration contents.
     * @param  string $configText the configuration content to write
     * @param  string $resquePath resquest component path
     * @throws Exception if the supervisord config file is not writtable or the resque bin file can not be found
     */
    private function configWorker($configText, $resquePath)
    {
        $supervisordConfigPath = Yii::app()->resque->supervisordConfigPath;
        !isset($supervisordConfigPath) && ($supervisordConfigPath = Constant::$SUPERVISORD_CONF);
        if (file_exists($supervisordConfigPath)
        && is_writable($supervisordConfigPath))
        {
            $fp = fopen($supervisordConfigPath, 'a');

            if (file_exists($resquePath.'/bin/resque'))
            {
                fwrite($fp, $configText);
                fclose($fp);
            }
            else
            {
                throw new Exception("Resque bin file is not found, please check the path.");
            }
        }
        else
        {
            throw new Exception("Can not find the supervisord config file or the file is not writtable.");
        }
    }

    private function getValueByType($redis, $key, $type)
    {
        switch ($type)
        {
            case 'set': return $redis->scard($key);
            case 'zset': return $redis->zcard($key);
            case 'list': return $redis->llen($key);
            default: return $redis->get($key);
        }
    }
}